using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation.Media;
using GT = Gadgeteer;

namespace Tyrannt
{

    // all map handling and maze drawing are handled by this class
    // there is a readonly View bitmap which will contain the currently
    // rendered maze view.
    [Serializable]
    class Maze
    {

        #region Internal Varianbles

        private char[][] maze = new char[22][];
        private char[][] mazeSegment = new char[3][];
        private int _currentLevel = 0;
        private Bitmap _view = new Bitmap(151,151);
        private Color _currentColour;

        private int _x = 0;
        private int _y = 0;
        private int _direction = 0;

        private string _mazePath;

        private GT.StorageDevice _storageDev;

        #endregion

        #region Constructor

        public Maze(GT.StorageDevice storageDev, string path,int level)
        {
            _storageDev = storageDev;
            
            _mazePath = path;
            _currentLevel = level;
            InitialiseMaze();
            LoadMaze();
            SetColour("white");
        }

        private void InitialiseMaze()
        {
            for (int lp = 0; lp <= 21; lp++)
            {
                maze[lp] = new char[22];
            }
            for (int y = 0; y <= 21; y++)
                for (int x = 0; x <= 21; x++)
                    maze[x][y] = ' ';
            for (int lp = 0; lp <= 2; lp++)
            {
                mazeSegment[lp] = new char[3];
            }
        }
        #endregion

        #region Properties
        public Bitmap View
        {
            get
            {
                GenerateSegment();
                RenderView();
                return _view;
            }
        }
        public string Path
        {
            set
            {
                _mazePath = value;
            }
        }
        public int Direction
        {
            get
            {
                return _direction;
            }
        }
        public int X
        {
            get
            {
                return _x;
            }
        }
        public int Y
        {
            get
            {
                return _y;
            }
        }
        public string Segment
        {
            get
            {
                string result="";
                for (int y = 0; y <= 2; y++)
                {
                    if (y != 0)
                        result += "\n";
                    for (int x = 0; x <= 2; x++)
                    {
                        char z = mazeSegment[x][y];
                        if (z == ' ')
                            z = '_';
                        result += z.ToString();
                    }
                }
                return result;
            }
        }
        #endregion

        #region Methods

        public void TurnLeft()
        {
            _direction--;
            if (_direction < 0)
                _direction = 3;
        }

        public void TurnRight()
        {
            _direction++;
            if (_direction > 3)
                _direction = 0;
        }

        public void MoveForward()
        {
            switch (_direction)
            {
                case 0:
                    if (maze[_x][_y - 1] != '#')
                        _y--;
                    break;
                case 1:
                    if (maze[_x + 1][_y] != '#')
                        _x++;
                    break;
                case 2:
                    if (maze[_x][_y + 1] != '#')
                        _y++;
                    break;
                case 3:
                    if (maze[_x - 1][_y] != '#')
                        _x--;
                    break;
            }
        }

        #endregion

        #region Render Routines

        void GenerateSegment()
        {
            switch (_direction)
            {
                case 0: // North
                    mazeSegment[0][0] = maze[_x + 1][_y];
                    mazeSegment[1][0] = maze[_x][_y];
                    mazeSegment[2][0] = maze[_x - 1][_y];
                    mazeSegment[0][1] = maze[_x + 1][_y - 1];
                    mazeSegment[1][1] = maze[_x][_y - 1];
                    mazeSegment[2][1] = maze[_x - 1][_y - 1];
                    mazeSegment[0][2] = maze[_x + 1][_y - 2];
                    mazeSegment[1][2] = maze[_x][_y - 2];
                    mazeSegment[2][2] = maze[_x - 1][_y - 2];
                    break;
                case 1: // East
                    mazeSegment[0][0] = maze[_x][_y + 1];
                    mazeSegment[1][0] = maze[_x][_y];
                    mazeSegment[2][0] = maze[_x][_y - 1];
                    mazeSegment[0][1] = maze[_x + 1][_y + 1];
                    mazeSegment[1][1] = maze[_x + 1][_y];
                    mazeSegment[2][1] = maze[_x + 1][_y - 1];
                    mazeSegment[0][2] = maze[_x + 2][_y + 1];
                    mazeSegment[1][2] = maze[_x + 2][_y];
                    mazeSegment[2][2] = maze[_x + 2][_y - 1];
                    break;
                case 2: // South
                    mazeSegment[0][0] = maze[_x - 1][_y];
                    mazeSegment[1][0] = maze[_x][_y];
                    mazeSegment[2][0] = maze[_x + 1][_y];
                    mazeSegment[0][1] = maze[_x - 1][_y + 1];
                    mazeSegment[1][1] = maze[_x][_y + 1];
                    mazeSegment[2][1] = maze[_x + 1][_y + 1];
                    mazeSegment[0][2] = maze[_x - 1][_y + 2];
                    mazeSegment[1][2] = maze[_x][_y + 2];
                    mazeSegment[2][2] = maze[_x + 1][_y + 2];
                    break;
                case 3: // West
                    mazeSegment[0][0] = maze[_x][_y - 1];
                    mazeSegment[1][0] = maze[_x][_y];
                    mazeSegment[2][0] = maze[_x][_y + 1];
                    mazeSegment[0][1] = maze[_x - 1][_y - 1];
                    mazeSegment[1][1] = maze[_x - 1][_y];
                    mazeSegment[2][1] = maze[_x - 1][_y + 1];
                    mazeSegment[0][2] = maze[_x - 2][_y - 1];
                    mazeSegment[1][2] = maze[_x - 2][_y];
                    mazeSegment[2][2] = maze[_x - 2][_y + 1];
                    break;
            }
        }

        private void RenderView()
        {
            MazeBorder();
            if (mazeSegment[0][0] == '#' || mazeSegment[0][0] == '=')
            {
                NearRightWall();
                if (mazeSegment[0][0] == '=')
                    NearRightDoor();
            }
            else
            {
                if (mazeSegment[0][1] == '#' || mazeSegment[0][1] == '=')
                {
                    NearRightTurn();
                    if (mazeSegment[0][1] == '=')
                        NearRightTurnDoor();
                }
            }
            if (mazeSegment[2][0] == '#' || mazeSegment[2][0] == '=')
            {
                NearLeftWall();
                if (mazeSegment[2][0] == '=')
                    NearLeftDoor();
            }
            else
            {
                if (mazeSegment[2][1] == '#' || mazeSegment[2][1] == '=')
                {
                    NearLeftTurn();
                    if (mazeSegment[2][1] == '=')
                        NearLeftTurnDoor();
                }
            }
            if (mazeSegment[1][1] != '#' && mazeSegment[1][1] != '=')
            {
                if (mazeSegment[0][1] == '#' || mazeSegment[0][1] == '=')
                {
                    RightWall();
                    if (mazeSegment[0][1] == '=')
                        RightDoor();
                }
                else
                {
                    if (mazeSegment[0][2] == '#')
                        if (mazeSegment[0][0] == '#')
                            RightTurnBlocked();
                        else
                            RightTurn();
                }
                if (mazeSegment[2][1] == '#' || mazeSegment[2][1] == '=')
                {
                    LeftWall();
                    if (mazeSegment[2][1] == '=')
                        LeftDoor();
                }
                else
                {
                    if (mazeSegment[2][2] == '#')
                        if (mazeSegment[0][2] == '#')
                            LeftTurnBlocked();
                        else
                            LeftTurn();
                }
                if (mazeSegment[1][2] == '#' || mazeSegment[1][2] == '=')
                {
                    FarWall();
                    if (mazeSegment[1][2] == '=')
                        FarDoor();
                }
            }
            if (mazeSegment[1][1] == '#' || mazeSegment[1][1] == '=')
            {
                NearWall();
                if (mazeSegment[1][1] == '=')
                    NearDoor();
            }
        }

        #endregion

        #region Maze Drawing Routines

        private void MazeBorder()
        {
            Clear();
            DrawLine(0, 0, 0, 150);
            DrawLine(0, 150, 150, 150);
            DrawLine(150, 150, 150, 0);
            DrawLine(150, 0, 0, 0);
        }

        private void NearLeftWall()
        {
            DrawLine(0, 0, 15, 15);
            DrawLine(15, 15, 15, 135);
            DrawLine(15, 135, 0, 150);
        }

        private void NearLeftDoor()
        {
            // TODO
            DrawLine(0, 20, 3, 23);
            DrawLine(3, 23, 3, 147);
        }

        private void NearRightWall()
        {
            DrawLine(150, 0, 135, 15);
            DrawLine(135, 15, 135, 135);
            DrawLine(135, 135, 150, 150);
        }

        private void NearRightDoor()
        {
            // TODO
            DrawLine(150, 20, 147, 23);
            DrawLine(147, 23, 147, 147);
        }

        private void NearLeftTurn()
        {
            DrawLine(0, 15, 15, 15);
            DrawLine(15, 15, 15, 135);
            DrawLine(15, 135, 0, 135);
        }

        private void NearLeftTurnDoor()
        {
            // TODO
            DrawLine(0, 23, 3, 23);
            DrawLine(3, 23, 3, 135);
        }

        private void NearRightTurn()
        {
            DrawLine(150, 15, 135, 15);
            DrawLine(135, 15, 135, 135);
            DrawLine(135, 135, 150, 135);
        }

        private void NearRightTurnDoor()
        {
            // TODO
            DrawLine(150, 23, 147, 23);
            DrawLine(147, 23, 147, 135);
        }

        private void NearWall()
        {
            DrawLine(15, 15, 15, 135);
            DrawLine(15, 135, 135, 135);
            DrawLine(135, 135, 135, 15);
            DrawLine(135, 15, 15, 15);
        }

        private void NearDoor()
        {
            DrawLine(40, 135, 40, 40);
            DrawLine(40, 40, 110, 40);
            DrawLine(110, 40, 110, 135);
            DrawCircle(90, 90, 2);
        }

        private void LeftWall()
        {
            DrawLine(15, 15, 50, 50);
            DrawLine(50, 50, 50, 100);
            DrawLine(50, 100, 15, 135);
            DrawLine(15, 135, 15, 15);
        }

        private void LeftDoor()
        {
            DrawLine(25, 125, 25, 37);
            DrawLine(25, 37, 45, 57);
            DrawLine(45, 57, 45, 105);
            DrawCircle(40, 80, 1);
        }

        private void RightWall()
        {
            DrawLine(135, 15, 100, 50);
            DrawLine(100, 50, 100, 100);
            DrawLine(100, 100, 135, 135);
            DrawLine(135, 135, 135, 15);
        }

        private void RightDoor()
        {
            DrawLine(125, 125, 125, 37);
            DrawLine(125, 37, 105, 57);
            DrawLine(105, 57, 105, 105);
            DrawCircle(120, 88, 2);
        }

        private void LeftTurnBlocked()
        {
            DrawLine(15, 50, 50, 50);
            DrawLine(50, 50, 50, 100);
            DrawLine(50, 100, 15, 100);
            DrawLine(15, 100, 15, 50);
        }

        private void RightTurnBlocked()
        {
            DrawLine(135, 50, 100, 50);
            DrawLine(100, 50, 100, 100);
            DrawLine(100, 100, 135, 100);
            DrawLine(135, 100, 135, 50);
        }

        private void LeftTurn()
        {
            DrawLine(0, 50, 50, 50);
            DrawLine(50, 50, 50, 100);
            DrawLine(50, 100, 0, 100);
            DrawLine(0, 100, 0, 50);
        }

        private void RightTurn()
        {
            DrawLine(150, 50, 100, 50);
            DrawLine(100, 50, 100, 100);
            DrawLine(100, 100, 150, 100);
            DrawLine(150, 100, 150, 50);
        }

        private void FarWall()
        {
            DrawLine(50, 50, 50, 100);
            DrawLine(50, 100, 100, 100);
            DrawLine(100, 100, 100, 50);
            DrawLine(100, 50, 50, 50);
        }

        private void FarDoor()
        {
            DrawLine(60, 100, 60, 60);
            DrawLine(60, 60, 90, 60);
            DrawLine(90, 60, 90, 100);
            DrawCircle(85, 85, 1);
        }
        #endregion

        #region IO Tools
        private void LoadMaze()
        {
            // read in the text file and convert to a string.
            string path = _mazePath + @"\store\" + _currentLevel + ".map";
            byte[] rawBytes = _storageDev.ReadFile(path);
            char[] rawCharacters = System.Text.UTF8Encoding.UTF8.GetChars(rawBytes);
            string tmpMap = new string(rawCharacters);
            // Now split the text file into lines using the \r character from the text file
            // note this will leave the \n character at the end of each line
            string[] lines = tmpMap.Split('\r');
            // Get the header, removing the trailing \n
            string header = lines[0].TrimEnd('\n');
            // check maze version and the dimensions of the maze
            string[] bits = header.Split('|');
            string ver = bits[0];
            int width = int.Parse(bits[1]);
            int height = int.Parse(bits[2]);
            if (ver != "1.0")
            {
                throw new Exception("Maze version is incorrect");
            }
            for (int y = 1; y <= height; y++)
            {
                char[] row = lines[y].Trim('\n').ToCharArray();
                for (int x = 0; x < width; x++)
                {
                    if (row[x] == '0')
                    {
                        _x = x + 1;
                        _y = y;
                        _direction = 2;
                    }
                    maze[x + 1][y] = row[x];
                }
            }
        }
        #endregion

        #region Bitmap Tools
        public void Clear()
        {
            _view.Clear();
        }

        public void SetColour(string colour)
        {
            switch (colour.ToLower())
            {
                case "yellow":
                    _currentColour = Colors.Yellow;
                    break;
                case "red":
                    _currentColour = Colors.Red;
                    break;
                case "black":
                    _currentColour = Colors.Black;
                    break;
                default:
                    _currentColour=Colors.White;
                    break;
            }
        }
        
        public void DrawLine(int x1, int y1, int x2, int y2)
        {
            _view.DrawLine(_currentColour, 1, x1, y1, x2, y2);
        }

        public void DrawCircle(int x, int y, int radius)
        {
            _view.DrawEllipse(_currentColour, x, y, radius, radius);
        }
        #endregion
    }
}
